package com.zhuhjay.service.system.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhuhjay.common.base.exception.BasicBusinessException;
import com.zhuhjay.common.base.util.BeanBuilder;
import com.zhuhjay.common.base.util.JacksonUtils;
import com.zhuhjay.common.redis.util.RedisUtils;
import com.zhuhjay.common.security.context.LoginUserContext;
import com.zhuhjay.service.system.mapper.SystemDirMapper;
import com.zhuhjay.service.system.model.domain.FileInfo;
import com.zhuhjay.service.system.model.domain.FolderInfo;
import com.zhuhjay.service.system.model.entity.SystemDir;
import com.zhuhjay.service.system.model.entity.SystemFile;
import com.zhuhjay.service.system.model.vo.DirInfoVo;
import com.zhuhjay.service.system.model.vo.FoldersVo;
import com.zhuhjay.service.system.service.SystemDirService;
import com.zhuhjay.service.system.service.SystemFileService;
import com.zhuhjay.service.system.service.SystemUserService;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.zhuhjay.common.base.constant.ApplicationConstant.FILE_SYSTEM_COMMON_DIR_ID;
import static com.zhuhjay.common.base.constant.ApplicationConstant.FILE_SYSTEM_ROOT_DIR_ID;
import static com.zhuhjay.common.base.constant.CacheConstant.FILESYSTEM_USER_DIRS_PREFIX;
import static com.zhuhjay.common.base.constant.CacheConstant.FILESYSTEM_USER_DIR_TEMP_PREFIX;
import static com.zhuhjay.common.base.constant.TimeConstant.HOUR;
import static com.zhuhjay.service.system.enums.DirPowerEnum.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author Zhuhjay
 * @since 2023-03-29
 */
@Service
public class SystemDirServiceImpl extends ServiceImpl<SystemDirMapper, SystemDir> implements SystemDirService {

    @Lazy
    @Resource
    private SystemFileService fileService;
    @Resource
    private SystemUserService userService;

    @Override
    public void create(String name, String power, String parentId) {
        Integer userId = LoginUserContext.getUserId();

        // 获取父文件夹信息
        SystemDir parentDir = this.lambdaQuery().eq(SystemDir::getId, parentId)
                .oneOpt().orElseThrow(() -> new BasicBusinessException("父文件夹不存在"));

        // 在 common 权限文件夹下进行文件夹创建
        if (COMMON.getPower().equals(parentDir.getPower())) {
            // 只有在 COMMON 目录下才能创建文件夹
            if (!FILE_SYSTEM_COMMON_DIR_ID.equals(parentId)) {
                throw new BasicBusinessException("只有在[公共]目录下才能创建文件夹");
            }
            // 判断是否创建重名目录
            this.lambdaQuery()
                    .eq(SystemDir::getName, name).eq(SystemDir::getParentId, parentId)
                    .oneOpt().ifPresent(dir -> {
                        throw new BasicBusinessException("该文件夹已存在");
                    });
            // 修订权限, 在 COMMON 目录下创建的文件夹权限为 common
            power = COMMON.getPower();
        }
        // 其他权限文件夹
        else {
            // 在不是 ROOT 目录的条件下 判断文件夹是否归属于该用户
            if (!FILE_SYSTEM_ROOT_DIR_ID.equals(parentId) && !userId.equals(parentDir.getUserId())) {
                throw new BasicBusinessException("该父文件夹不存在");
            }
            // 判断该用户是否在同级目录下创建同名文件夹
            this.lambdaQuery()
                    .eq(SystemDir::getUserId, userId).eq(SystemDir::getName, name)
                    .eq(SystemDir::getParentId, parentId)
                    .oneOpt().ifPresent(dir -> {
                        throw new BasicBusinessException("该文件夹已存在");
                    });
            // 判断权限是否合法
            if (COMMON.getPower().equals(power)) {
                throw new BasicBusinessException("权限不合法");
            }
        }

        // 创建文件夹
        SystemDir dir = new SystemDir();
        dir.setName(name);
        dir.setPower(power);
        dir.setUserId(userId);
        dir.setParentId(parentId);
        save(dir);

        // 删除缓存(用户文件夹列表缓存)
        RedisUtils.deleteCache(FILESYSTEM_USER_DIRS_PREFIX + userId);
    }

    @Override
    public void delete(String id) {
        Integer userId = LoginUserContext.getUserId();
        // 判断该用户是否存在该文件夹
        this.lambdaQuery()
                .eq(SystemDir::getUserId, userId).eq(SystemDir::getId, id)
                .oneOpt().orElseThrow(() -> new BasicBusinessException("文件夹不存在或不属于该用户"));

        // 如果该文件夹下还有文件或者文件夹, 那么就不能够删除
        Long fileCount = fileService.lambdaQuery().eq(SystemFile::getDirId, id).count();
        if (fileCount != null && fileCount > 0) {
            throw new BasicBusinessException("该文件夹下还有文件(或存在于回收站), 不能删除");
        }
        Long folderCount = this.lambdaQuery().eq(SystemDir::getParentId, id).count();
        if (folderCount != null && folderCount > 0) {
            throw new BasicBusinessException("该文件夹下还有文件夹, 不能删除");
        }
        // 删除文件夹
        removeById(id);
        // 删除缓存(用户文件夹列表缓存)
        RedisUtils.deleteCache(FILESYSTEM_USER_DIRS_PREFIX + userId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void change(String id, String name, String power) {
        Integer userId = LoginUserContext.getUserId();
        // 判断该用户是否存在该文件夹
        SystemDir dirInfo = this.lambdaQuery()
                .eq(SystemDir::getUserId, userId).eq(SystemDir::getId, id)
                .oneOpt().orElseThrow(() -> new BasicBusinessException("文件夹不存在或不属于该用户"));

        // 用于修改文件夹信息
        SystemDir dir = new SystemDir();
        dir.setId(id);
        dir.setName(name);
        dir.setPower(power);

        // 判断是否进行权限的修改
        if (!power.equals(dirInfo.getPower())) {
            // 不能进行权限降级 (common -> public | private)
            if (COMMON.getPower().equals(dirInfo.getPower())) {
                throw new BasicBusinessException("操作不允许");
            }
            // 如果是权限升级 (private | public -> common)
            if (COMMON.getPower().equals(power)) {
                // 需要移动目录位置到 COMMON 目录下
                // 判断是否存在同名文件夹
                Long count = this.lambdaQuery()
                        .eq(SystemDir::getParentId, FILE_SYSTEM_COMMON_DIR_ID)
                        // 文件夹是否改名
                        .eq(SystemDir::getName, name.equals(dirInfo.getName()) ? dirInfo.getName() : name)
                        .count();
                if (count != null && count > 0) {
                    throw new BasicBusinessException("文件夹名[" + name + "]已存在");
                }
                // 该文件夹下存在目录同样不能够移动
                Long folderCount = this.lambdaQuery().eq(SystemDir::getParentId, id).count();
                if (folderCount != null && folderCount > 0) {
                    throw new BasicBusinessException("该文件夹下还有文件夹, 不能提升权限");
                }
                // 修改文件夹信息
                // 移动到 COMMON 目录下
                dir.setParentId(FILE_SYSTEM_COMMON_DIR_ID);
                updateById(dir);
                // 删除缓存(用户文件夹列表缓存)
                RedisUtils.deleteCache(FILESYSTEM_USER_DIRS_PREFIX + userId);
                return;
            }
        }

        // 判断该用户是否在同级目录下创建同名文件夹
        Long count = this.lambdaQuery()
                .ne(SystemDir::getId, id)
                .eq(SystemDir::getUserId, userId)
                .eq(SystemDir::getName, name).eq(SystemDir::getParentId, dirInfo.getParentId())
                .count();
        if (count != null && count > 0) {
            throw new BasicBusinessException("文件夹名[" + name + "]已存在");
        }
        // 修改文件夹信息
        updateById(dir);
        // 删除缓存(用户文件夹列表缓存)
        RedisUtils.deleteCache(FILESYSTEM_USER_DIRS_PREFIX + userId);
    }

    @Override
    public DirInfoVo get(String dirId) {
        // 获取用户自己在当前目录id下的所有文件夹以及文件
        Integer userId = LoginUserContext.getUserId();
        SystemDir dirInfo;
        // ROOT / COMMON 文件夹不需要判断用户
        if (!FILE_SYSTEM_ROOT_DIR_ID.equals(dirId) && !FILE_SYSTEM_COMMON_DIR_ID.equals(dirId)) {
            // 先判断该文件夹是否存在
            dirInfo = this.lambdaQuery().eq(SystemDir::getId, dirId)
                    .oneOpt().orElseThrow(() -> new BasicBusinessException("该文件夹不存在"));
            // 判断当前目录是否有 common 权限
            if (!COMMON.getPower().equals(dirInfo.getPower())) {
                // 没有 common 权限, 判断当前文件夹是否属于该用户
                if (!userId.equals(dirInfo.getUserId())) {
                    throw new BasicBusinessException("文件夹不存在或不属于该用户");
                }
            }
        } else {
            dirInfo = this.lambdaQuery().eq(SystemDir::getId, dirId).one();
        }
        return genericDirInfoVo(dirId, dirInfo, false);
    }

    @Override
    public FoldersVo folderList() {
        Integer userId = LoginUserContext.getUserId();
        // 使用缓存
        String key = FILESYSTEM_USER_DIRS_PREFIX + userId;
        String cache = RedisUtils.getCache(key);
        if (StrUtil.isNotBlank(cache)) {
            return JacksonUtils.readValue(cache, FoldersVo.class);
        }
        FoldersVo foldersVo = getFolders(userId);
        RedisUtils.setCacheAndExpireRan(key, foldersVo, HOUR, TimeUnit.MILLISECONDS);
        return foldersVo;
    }

    @Override
    public DirInfoVo getPublic(String dirId) {
        // 判断该文件夹是否存在, 并且为公开/公共文件夹
        SystemDir dirInfo = this.lambdaQuery().eq(SystemDir::getId, dirId)
                .and(wrapper -> wrapper
                        .eq(SystemDir::getPower, PUBLIC.getPower())
                        .or()
                        .eq(SystemDir::getPower, COMMON.getPower())
                ).oneOpt().orElseThrow(() -> new BasicBusinessException("该文件夹不存在"));
        return genericDirInfoVo(dirId, dirInfo, true);
    }

    /**
     * 封装文件夹信息
     * @param dirId 文件夹id
     * @param dirInfo 文件夹信息
     * @param isPublic 是否公开(登录/未登录)
     * @return 文件夹信息
     */
    private DirInfoVo genericDirInfoVo(String dirId, SystemDir dirInfo, boolean isPublic) {
        // 封装文件夹信息
        DirInfoVo dirInfoVo = new DirInfoVo();

        // 获取文件夹信息
        dirInfoVo.setFolder(FolderInfo.copyTo(dirInfo, userService.getCreateUserByCaffeine(dirInfo.getUserId())));

        // 获取同级可打开的目录
        List<FolderInfo> parentFolders = new ArrayList<>();
        if (!FILE_SYSTEM_ROOT_DIR_ID.equals(dirId)) {
            List<SystemDir> list = this.lambdaQuery()
                    .eq(SystemDir::getParentId, dirInfo.getParentId())
                    // 公共 需要排除
                    .ne(isPublic, SystemDir::getPower, PRIVATE.getPower())
                    .and(!isPublic, wrapper -> {
                        // 非公共 获取登录用户 或者 common 权限目录
                        wrapper.eq(SystemDir::getPower, COMMON.getPower())
                                .or()
                                .eq(SystemDir::getUserId, LoginUserContext.getUserId());
                    })
                    // 排序
                    .orderByAsc(SystemDir::getPower)
                    .orderByDesc(SystemDir::getCreateTime)
                    .orderByAsc(SystemDir::getName).list();
            list.forEach(dir -> parentFolders.add(FolderInfo.copyTo(dir, userService.getCreateUserByCaffeine(dir.getUserId()))));
        } else {
            // 当前目录是 ROOT 目录, 那么就只有一个父目录
            parentFolders.add(this.getFolderPaths(FILE_SYSTEM_ROOT_DIR_ID).get(0));
        }
        dirInfoVo.setParentFolders(parentFolders);

        // 获取当前文件夹的目录
        dirInfoVo.setPath(this.getFolderPaths(dirId));

        // 获取文件夹下的文件夹, 并封装
        getFoldersInfo2Vo(isPublic ? null : LoginUserContext.getUserId(), dirInfoVo);

        //获取该文件夹下的文件, 并封装
        getFiles2Vo(isPublic ? null : LoginUserContext.getUserId(), dirInfoVo);
        return dirInfoVo;
    }

    @Override
    public List<FolderInfo> getFolderPaths(String id) {
        String currentDirId = id;
        String keyTemplate = FILESYSTEM_USER_DIR_TEMP_PREFIX + "{}";
        List<FolderInfo> paths = new ArrayList<>();
        while (currentDirId != null) {
            String key = StrUtil.format(keyTemplate, currentDirId);
            String cache = RedisUtils.getCache(key);
            SystemDir dir;
            if (StrUtil.isBlank(cache)) {
                dir = this.getById(currentDirId);
                RedisUtils.setCacheAndExpireRan(key, dir, 5, TimeUnit.MINUTES);
            } else {
                dir = JacksonUtils.readValue(cache, SystemDir.class);
            }
            paths.add(FolderInfo.copyTo(dir, userService.getCreateUserByCaffeine(dir.getUserId())));
            currentDirId = dir.getParentId();
        }
        Collections.reverse(paths);
        return paths;
    }

    /**
     * 获取文件夹列表(只包含id和name 以及该文件夹的子文件夹)
     *
     * @param userId 所属用户id
     * @return 文件夹列表 -> 文件夹树
     */
    private FoldersVo getFolders(Integer userId) {
        SystemDir root = this.lambdaQuery().eq(SystemDir::getId, FILE_SYSTEM_ROOT_DIR_ID).one();
        FoldersVo foldersVo = BeanBuilder.builder(FoldersVo::new)
                .with(FoldersVo::setId, root.getId())
                .with(FoldersVo::setName, root.getName())
                .with(FoldersVo::setChildren, new ArrayList<>())
                .build();
        // 使用层序遍历来获取完整的目录
        Queue<FoldersVo> queue = new LinkedList<>();
        queue.offer(foldersVo);
        while (!queue.isEmpty()) {
            // 获取当前层的子文件夹
            FoldersVo parentFolder = queue.poll();
            List<SystemDir> list = this.lambdaQuery()
                    .eq(SystemDir::getParentId, parentFolder.getId())
                    .and(wrapper -> wrapper
                            .eq(SystemDir::getUserId, userId)
                            .or()
                            .eq(SystemDir::getPower, COMMON.getPower())
                    )
                    .orderByDesc(SystemDir::getCreateTime)
                    .orderByAsc(List.of(SystemDir::getPower, SystemDir::getName)).list();
            // 将当前层的子文件夹存储, 并让子目录进入下一层获取
            list.forEach(folder -> {
                FoldersVo build = BeanBuilder.builder(FoldersVo::new)
                        .with(FoldersVo::setId, folder.getId())
                        .with(FoldersVo::setName, folder.getName())
                        .with(FoldersVo::setChildren, new ArrayList<>())
                        .build();
                parentFolder.getChildren().add(build);
                queue.offer(build);
            });
        }
        return foldersVo;
    }

    /**
     * 获取文件夹下的文件, 并封装
     *
     * @param userId    文件夹持有者id | null
     * @param dirInfoVo 文件夹信息
     */
    private void getFiles2Vo(Integer userId, DirInfoVo dirInfoVo) {
        FolderInfo folderInfo = dirInfoVo.getFolder();
        List<FileInfo> files = new ArrayList<>();
        // 如果该目录有 common 权限
        if (COMMON.getPower().equals(folderInfo.getPower())) {
            // 获取公共文件信息
            fileService.lambdaQuery().eq(SystemFile::getDirId, folderInfo.getId())
                    .eq(SystemFile::getStatus, 1)
                    .orderByDesc(SystemFile::getCreateTime)
                    // 文件按名称升序排列
                    .orderByAsc(SystemFile::getRealName)
                    .list().forEach(file -> files.add(FileInfo.copyTo(file, userService.getCreateUserByCaffeine(file.getUserId()))));
        }
        // 既然目录有 common 权限, 证明处于公共目录的范畴, 根本不需要获取其他权限的文件(因为根本不存在)
        else {
            // 此时不在 common 权限的目录下
            fileService.lambdaQuery()
                    // 获取公开文件不需要判断用户
                    .eq(userId != null, SystemFile::getUserId, userId)
                    .eq(SystemFile::getDirId, folderInfo.getId())
                    .eq(SystemFile::getStatus, 1)
                    .orderByDesc(SystemFile::getCreateTime)
                    // 文件按名称升序排列
                    .orderByAsc(SystemFile::getRealName)
                    .list().forEach(file -> files.add(FileInfo.copyTo(file, userService.getCreateUserByCaffeine(file.getUserId()))));
        }
        dirInfoVo.setFiles(files);
    }

    /**
     * 获取文件夹下的文件夹, 并封装
     *
     * @param userId    文件夹持有者id | null
     * @param dirInfoVo 文件夹信息
     */
    private void getFoldersInfo2Vo(Integer userId, DirInfoVo dirInfoVo) {
        FolderInfo folderInfo = dirInfoVo.getFolder();
        List<FolderInfo> folders = new ArrayList<>();
        // 如果该目录有 common 权限, 或者是 ROOT 那么就需要获取到公共文件夹信息
        if (COMMON.getPower().equals(folderInfo.getPower()) || FILE_SYSTEM_ROOT_DIR_ID.equals(folderInfo.getId())) {
            // 获取公共文件夹信息
            this.lambdaQuery().eq(SystemDir::getParentId, folderInfo.getId())
                    .eq(SystemDir::getPower, COMMON.getPower())
                    .orderByDesc(SystemDir::getCreateTime)
                    // 文件按名称升序排列
                    .orderByAsc(SystemDir::getName)
                    .list().forEach(dir -> folders.add(FolderInfo.copyTo(dir, userService.getCreateUserByCaffeine(dir.getUserId()))));
        }
        // userId == null 说明是公共文件夹, 不需要判断
        this.lambdaQuery()
                .eq(userId != null, SystemDir::getUserId, userId)
                .eq(SystemDir::getParentId, folderInfo.getId())
                // 排除公共文件夹
                .ne(SystemDir::getPower, COMMON.getPower())
                // 只获取公开文件夹
                .eq(userId == null, SystemDir::getPower, PUBLIC.getPower())
                .orderByDesc(SystemDir::getCreateTime)
                // 文件夹按名称升序排列
                .orderByAsc(SystemDir::getName)
                .list().forEach(dir -> folders.add(FolderInfo.copyTo(dir, userService.getCreateUserByCaffeine(dir.getUserId()))));
        dirInfoVo.setFolders(folders);
    }

}
